Add a cross-platform "tray icon" API, by porting EggStatusIcon/EggTrayIcon
authorMatthias Clasen <mclasen@redhat.com>
Mon, 29 Aug 2005 17:47:10 +0000 (17:47 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Mon, 29 Aug 2005 17:47:10 +0000 (17:47 +0000)
2005-08-29  Matthias Clasen  <mclasen@redhat.com>

Add a cross-platform "tray icon" API, by
porting EggStatusIcon/EggTrayIcon (#105101)

* gtk/gtkstatusicon.h: A GtkStatusIcon is an object which
displays an icon in a notification area.

* gtk/gtkstatusicon-x11.c: GtkStatusIcon implementation for
X11, using GtkTrayIcon.

* gtk/gtktrayicon.h:
* gtk/gtktrayicon.c: An implementation of the freedesktop.org
system tray specification, not public API.

* gtk/gtk.symbols: Add new exported functions.

* gtk/gtk.h: Include gtkstatusicon.h.

* gtk/Makefile.am: Add new files.

* tests/Makefile.am:
* tests/teststatusicon.c: Test for GtkStatusIcon.

ChangeLog
ChangeLog.pre-2-10
gtk/Makefile.am
gtk/gtk.h
gtk/gtk.symbols
gtk/gtkstatusicon.c [new file with mode: 0755]
gtk/gtkstatusicon.h [new file with mode: 0755]
gtk/gtktrayicon-x11.c [new file with mode: 0755]
gtk/gtktrayicon.h [new file with mode: 0755]
tests/Makefile.am
tests/teststatusicon.c [new file with mode: 0755]

index 95d729d0aeb4092c58db49a011dc4122c4e3df8b..ff20bae7c3513df9de58ebe7706da318f844a499 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,32 @@
+2005-08-29  Matthias Clasen  <mclasen@redhat.com>
+
+       Add a cross-platform "tray icon" API, by
+       porting EggStatusIcon/EggTrayIcon (#105101)
+       
+       * gtk/gtkstatusicon.h: A GtkStatusIcon is an object which
+       displays an icon in a notification area.
+
+       * gtk/gtkstatusicon-x11.c: GtkStatusIcon implementation for
+       X11, using GtkTrayIcon.
+
+       * gtk/gtktrayicon.h: 
+       * gtk/gtktrayicon.c: An implementation of the freedesktop.org
+       system tray specification, not public API.
+
+       * gtk/gtk.symbols: Add new exported functions.
+
+       * gtk/gtk.h: Include gtkstatusicon.h.
+
+       * gtk/Makefile.am: Add new files.
+
+       * tests/Makefile.am:
+       * tests/teststatusicon.c: Test for GtkStatusIcon.
+
 2005-08-29  Christopher Aillon  <caillon@redhat.com>
 
        * gtk/gtkstock.c: Add builtin GtkStockItems for GTK_STOCK_CONNECT
        and GTK_STOCK_DISCONNECT
-
+       
 2005-08-29  Matthias Clasen  <mclasen@redhat.com>
 
        * gtk/gtksocket-x11.c (_gtk_socket_windowing_embed_notify): 
index 95d729d0aeb4092c58db49a011dc4122c4e3df8b..ff20bae7c3513df9de58ebe7706da318f844a499 100644 (file)
@@ -1,8 +1,32 @@
+2005-08-29  Matthias Clasen  <mclasen@redhat.com>
+
+       Add a cross-platform "tray icon" API, by
+       porting EggStatusIcon/EggTrayIcon (#105101)
+       
+       * gtk/gtkstatusicon.h: A GtkStatusIcon is an object which
+       displays an icon in a notification area.
+
+       * gtk/gtkstatusicon-x11.c: GtkStatusIcon implementation for
+       X11, using GtkTrayIcon.
+
+       * gtk/gtktrayicon.h: 
+       * gtk/gtktrayicon.c: An implementation of the freedesktop.org
+       system tray specification, not public API.
+
+       * gtk/gtk.symbols: Add new exported functions.
+
+       * gtk/gtk.h: Include gtkstatusicon.h.
+
+       * gtk/Makefile.am: Add new files.
+
+       * tests/Makefile.am:
+       * tests/teststatusicon.c: Test for GtkStatusIcon.
+
 2005-08-29  Christopher Aillon  <caillon@redhat.com>
 
        * gtk/gtkstock.c: Add builtin GtkStockItems for GTK_STOCK_CONNECT
        and GTK_STOCK_DISCONNECT
-
+       
 2005-08-29  Matthias Clasen  <mclasen@redhat.com>
 
        * gtk/gtksocket-x11.c (_gtk_socket_windowing_embed_notify): 
index 5f94330810f35aca870cc05d4f1ecf5ba3e96505..4ea8eda68d65d2c88a412dd4785582e018200af5 100644 (file)
@@ -239,6 +239,7 @@ gtk_public_h_sources =          \
        gtksocket.h             \
        gtkspinbutton.h         \
        gtkstatusbar.h          \
+       gtkstatusicon.h         \
        gtkstock.h              \
        gtkstyle.h              \
        gtktable.h              \
@@ -535,8 +536,13 @@ gtk_c_sources +=         gtkfilesystemwin32.c
 endif
 
 if USE_X11
-gtk_private_h_sources += gtkxembed.h
-gtk_c_sources +=         gtkplug-x11.c gtksocket-x11.c gtkxembed.c
+gtk_private_h_sources += gtkxembed.h gtktrayicon.h
+gtk_c_sources += \
+       gtkplug-x11.c   \
+       gtksocket-x11.c \
+       gtkxembed.c     \
+       gtktrayicon.c   \
+       gtkstatusicon-x11.c
 else
 if USE_WIN32
 gtk_private_h_sources += gtkwin32embed.h
index 17249f5c8d37c19315eb9e2ed8b706d336cea94b..174333fc0f81691761bfac6f8e2460765e8540b0 100644 (file)
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
 #include <gtk/gtksocket.h>
 #include <gtk/gtkspinbutton.h>
 #include <gtk/gtkstatusbar.h>
+#include <gtk/gtkstatusicon.h>
 #include <gtk/gtkstock.h>
 #include <gtk/gtkstyle.h>
 #include <gtk/gtktable.h>
index 9165813970e2ceb87179a9a9f02aedbb6dafd18e..f0ea5cbdbb461fbb8b5ba8e84cbee04437ab600e 100644 (file)
@@ -973,6 +973,32 @@ gtk_drag_unhighlight
 #endif
 #endif
 
+#if IN_HEADER(__GTK_STATUS_ICON_H__)
+#if IN_FILE(__GTK_STATUS_ICON_C__)
+gtk_status_icon_get_type G_GNUC_CONST
+gtk_status_icon_new
+gtk_status_icon_new_from_pixbuf
+gtk_status_icon_new_from_file
+gtk_status_icon_new_from_stock
+gtk_status_icon_new_from_icon_name
+gtk_status_icon_set_from_pixbuf
+gtk_status_icon_set_from_file
+gtk_status_icon_set_from_stock
+gtk_status_icon_set_from_icon_name
+gtk_status_icon_get_storage_type
+gtk_status_icon_get_pixbuf
+gtk_status_icon_get_stock
+gtk_status_icon_get_icon_name
+gtk_status_icon_get_size
+gtk_status_icon_set_tooltip
+gtk_status_icon_set_visible
+gtk_status_icon_get_visible
+gtk_status_icon_set_blinking
+gtk_status_icon_get_blinking
+gtk_status_icon_is_embedded
+#endif
+#endif
+
 #if IN_HEADER(__GTK_STYLE_H__)
 #if IN_FILE(__GTK_STYLE_C__)
 #ifndef GTK_DISABLE_DEPRECATED
diff --git a/gtk/gtkstatusicon.c b/gtk/gtkstatusicon.c
new file mode 100755 (executable)
index 0000000..490b5aa
--- /dev/null
@@ -0,0 +1,925 @@
+/* gtkstatusicon-x11.c:
+ *
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin <mark@skynet.ie>
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "gtkstatusicon.h"
+
+#include "gtkintl.h"
+#include "gtkiconfactory.h"
+#include "gtkmarshalers.h"
+#include "gtktooltips.h"
+#include "gtktrayicon.h"
+
+#include "gtkprivate.h"
+#include "gtkwidget.h"
+
+#include "gtkalias.h"
+
+#define BLINK_TIMEOUT 500
+
+enum
+{
+  PROP_0,
+  PROP_PIXBUF,
+  PROP_FILE,
+  PROP_STOCK,
+  PROP_ICON_NAME,
+  PROP_STORAGE_TYPE,
+  PROP_SIZE,
+  PROP_VISIBLE,
+  PROP_BLINKING
+};
+
+enum 
+{
+  ACTIVATE_SIGNAL,
+  POPUP_MENU_SIGNAL,
+  SIZE_CHANGED_SIGNAL,
+  LAST_SIGNAL
+};
+
+struct _GtkStatusIconPrivate
+{
+  GtkWidget    *tray_icon;
+  GtkWidget    *image;
+  gint          size;
+
+  gint          image_width;
+  gint          image_height;
+
+  GtkTooltips  *tooltips;
+
+  GtkImageType  storage_type;
+
+  union
+    {
+      GdkPixbuf *pixbuf;
+      gchar     *stock_id;
+      gchar     *icon_name;
+    } image_data;
+
+  GdkPixbuf    *blank_icon;
+  guint         blinking_timeout;
+
+  guint         blinking : 1;
+  guint         blink_off : 1;
+  guint         visible : 1;
+};
+
+static void     gtk_status_icon_finalize         (GObject        *object);
+static void     gtk_status_icon_set_property     (GObject        *object,
+                                                 guint           prop_id,
+                                                 const GValue   *value,
+                                                 GParamSpec     *pspec);
+static void     gtk_status_icon_get_property     (GObject        *object,
+                                                 guint           prop_id,
+                                                 GValue         *value,
+                                                 GParamSpec     *pspec);
+
+static void     gtk_status_icon_size_allocate    (GtkStatusIcon  *status_icon,
+                                                 GtkAllocation  *allocation);
+static gboolean gtk_status_icon_button_press     (GtkStatusIcon  *status_icon,
+                                                 GdkEventButton *event);
+static void     gtk_status_icon_disable_blinking (GtkStatusIcon  *status_icon);
+static void     gtk_status_icon_reset_image_data (GtkStatusIcon  *status_icon);
+                                          
+
+static guint status_icon_signals [LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GtkStatusIcon, gtk_status_icon, G_TYPE_OBJECT);
+
+static void
+gtk_status_icon_class_init (GtkStatusIconClass *class)
+{
+  GObjectClass *gobject_class = (GObjectClass *) class;
+
+  gobject_class->finalize     = gtk_status_icon_finalize;
+  gobject_class->set_property = gtk_status_icon_set_property;
+  gobject_class->get_property = gtk_status_icon_get_property;
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_PIXBUF,
+                                  g_param_spec_object ("pixbuf",
+                                                       P_("Pixbuf"),
+                                                       P_("A GdkPixbuf to display"),
+                                                       GDK_TYPE_PIXBUF,
+                                                       GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_FILE,
+                                  g_param_spec_string ("file",
+                                                       P_("Filename"),
+                                                       P_("Filename to load and display"),
+                                                       NULL,
+                                                       GTK_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_STOCK,
+                                  g_param_spec_string ("stock",
+                                                       P_("Stock ID"),
+                                                       P_("Stock ID for a stock image to display"),
+                                                       NULL,
+                                                       GTK_PARAM_READWRITE));
+  
+  g_object_class_install_property (gobject_class,
+                                   PROP_ICON_NAME,
+                                   g_param_spec_string ("icon-name",
+                                                        P_("Icon Name"),
+                                                        P_("The name of the icon from the icon theme"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+  
+  g_object_class_install_property (gobject_class,
+                                  PROP_STORAGE_TYPE,
+                                  g_param_spec_enum ("storage-type",
+                                                     P_("Storage type"),
+                                                     P_("The representation being used for image data"),
+                                                     GTK_TYPE_IMAGE_TYPE,
+                                                     GTK_IMAGE_EMPTY,
+                                                     GTK_PARAM_READABLE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_SIZE,
+                                  g_param_spec_int ("size",
+                                                    P_("Size"),
+                                                    P_("The size of the icon"),
+                                                    0,
+                                                    G_MAXINT,
+                                                    0,
+                                                    GTK_PARAM_READABLE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_BLINKING,
+                                  g_param_spec_boolean ("blinking",
+                                                        P_("Blinking"),
+                                                        P_("Whether or not the status icon is blinking"),
+                                                        FALSE,
+                                                        GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_VISIBLE,
+                                  g_param_spec_boolean ("visible",
+                                                        P_("Visible"),
+                                                        P_("Whether or not the status icon is visible"),
+                                                        TRUE,
+                                                        GTK_PARAM_READWRITE));
+
+
+  status_icon_signals [ACTIVATE_SIGNAL] =
+    g_signal_new ("activate",
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                 G_STRUCT_OFFSET (GtkStatusIconClass, activate),
+                 NULL,
+                 NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE,
+                 0);
+
+  status_icon_signals [POPUP_MENU_SIGNAL] =
+    g_signal_new ("popup-menu",
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                 G_STRUCT_OFFSET (GtkStatusIconClass, popup_menu),
+                 NULL,
+                 NULL,
+                 _gtk_marshal_VOID__UINT_UINT,
+                 G_TYPE_NONE,
+                 2,
+                 G_TYPE_UINT,
+                 G_TYPE_UINT);
+
+  status_icon_signals [SIZE_CHANGED_SIGNAL] =
+    g_signal_new ("size-changed",
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_FIRST,
+                 G_STRUCT_OFFSET (GtkStatusIconClass, size_changed),
+                 NULL,
+                 NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_INT);
+
+  g_type_class_add_private (class, sizeof (GtkStatusIconPrivate));
+}
+
+static void
+gtk_status_icon_init (GtkStatusIcon *status_icon)
+{
+  status_icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (status_icon, GTK_TYPE_STATUS_ICON,
+                                                  GtkStatusIconPrivate);
+  
+  status_icon->priv->storage_type = GTK_IMAGE_EMPTY;
+  status_icon->priv->size       = 0;
+  status_icon->priv->image_width = 0;
+  status_icon->priv->image_height = 0;
+  status_icon->priv->visible    = TRUE;
+
+  status_icon->priv->tray_icon = GTK_WIDGET (_gtk_tray_icon_new (NULL));
+
+  gtk_widget_add_events (GTK_WIDGET (status_icon->priv->tray_icon),
+                        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+
+  g_signal_connect_swapped (status_icon->priv->tray_icon, "button-press-event",
+                           G_CALLBACK (gtk_status_icon_button_press), status_icon);
+
+  status_icon->priv->image = gtk_image_new ();
+  gtk_container_add (GTK_CONTAINER (status_icon->priv->tray_icon),
+                    status_icon->priv->image);
+
+  g_signal_connect_swapped (status_icon->priv->image, "size-allocate",
+                           G_CALLBACK (gtk_status_icon_size_allocate), status_icon);
+
+  gtk_widget_show (status_icon->priv->image);
+  gtk_widget_show (status_icon->priv->tray_icon);
+
+  status_icon->priv->tooltips = gtk_tooltips_new ();
+  g_object_ref (status_icon->priv->tooltips);
+  gtk_object_sink (GTK_OBJECT (status_icon->priv->tooltips));
+}
+
+static void
+gtk_status_icon_finalize (GObject *object)
+{
+  GtkStatusIcon *status_icon = GTK_STATUS_ICON (object);
+
+  gtk_status_icon_disable_blinking (status_icon);
+  
+  gtk_status_icon_reset_image_data (status_icon);
+
+  if (status_icon->priv->blank_icon)
+    g_object_unref (status_icon->priv->blank_icon);
+  status_icon->priv->blank_icon = NULL;
+
+  if (status_icon->priv->tooltips)
+    g_object_unref (status_icon->priv->tooltips);
+  status_icon->priv->tooltips = NULL;
+
+  gtk_widget_destroy (status_icon->priv->tray_icon);
+
+  G_OBJECT_CLASS (gtk_status_icon_parent_class)->finalize (object);
+}
+
+static void
+gtk_status_icon_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GtkStatusIcon *status_icon = GTK_STATUS_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_PIXBUF:
+      gtk_status_icon_set_from_pixbuf (status_icon, g_value_get_object (value));
+      break;
+    case PROP_FILE:
+      gtk_status_icon_set_from_file (status_icon, g_value_get_string (value));
+      break;
+    case PROP_STOCK:
+      gtk_status_icon_set_from_stock (status_icon, g_value_get_string (value));
+      break;
+    case PROP_ICON_NAME:
+      gtk_status_icon_set_from_icon_name (status_icon, g_value_get_string (value));
+      break;
+    case PROP_BLINKING:
+      gtk_status_icon_set_blinking (status_icon, g_value_get_boolean (value));
+      break;
+    case PROP_VISIBLE:
+      gtk_status_icon_set_visible (status_icon, g_value_get_boolean (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_status_icon_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GtkStatusIcon *status_icon = GTK_STATUS_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_PIXBUF:
+      g_value_set_object (value, gtk_status_icon_get_pixbuf (status_icon));
+      break;
+    case PROP_STOCK:
+      g_value_set_string (value, gtk_status_icon_get_stock (status_icon));
+      break;
+    case PROP_ICON_NAME:
+      g_value_set_string (value, gtk_status_icon_get_icon_name (status_icon));
+      break;
+    case PROP_STORAGE_TYPE:
+      g_value_set_enum (value, gtk_status_icon_get_storage_type (status_icon));
+      break;
+    case PROP_SIZE:
+      g_value_set_int (value, gtk_status_icon_get_size (status_icon));
+      break;
+    case PROP_BLINKING:
+      g_value_set_boolean (value, gtk_status_icon_get_blinking (status_icon));
+      break;
+    case PROP_VISIBLE:
+      g_value_set_boolean (value, gtk_status_icon_get_visible (status_icon));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+GtkStatusIcon *
+gtk_status_icon_new (void)
+{
+  return g_object_new (GTK_TYPE_STATUS_ICON, NULL);
+}
+
+GtkStatusIcon *
+gtk_status_icon_new_from_pixbuf (GdkPixbuf *pixbuf)
+{
+  return g_object_new (GTK_TYPE_STATUS_ICON,
+                      "pixbuf", pixbuf,
+                      NULL);
+}
+
+GtkStatusIcon *
+gtk_status_icon_new_from_file (const gchar *filename)
+{
+  return g_object_new (GTK_TYPE_STATUS_ICON,
+                      "file", filename,
+                      NULL);
+}
+
+GtkStatusIcon *
+gtk_status_icon_new_from_stock (const gchar *stock_id)
+{
+  return g_object_new (GTK_TYPE_STATUS_ICON,
+                      "stock", stock_id,
+                      NULL);
+}
+
+GtkStatusIcon *
+gtk_status_icon_new_from_icon_name (const gchar *icon_name)
+{
+  return g_object_new (GTK_TYPE_STATUS_ICON,
+                      "icon-name", icon_name,
+                      NULL);
+}
+
+static void
+emit_activate_signal (GtkStatusIcon *status_icon)
+{
+  g_signal_emit (status_icon,
+                status_icon_signals [ACTIVATE_SIGNAL], 0);
+}
+
+static void
+emit_popup_menu_signal (GtkStatusIcon *status_icon,
+                       guint          button,
+                       guint32        activate_time)
+{
+  g_signal_emit (status_icon,
+                status_icon_signals [POPUP_MENU_SIGNAL], 0,
+                button,
+                activate_time);
+}
+
+static gboolean
+emit_size_changed_signal (GtkStatusIcon *status_icon,
+                         gint           size)
+{
+  gboolean handled = FALSE;
+  
+  g_signal_emit (status_icon,
+                status_icon_signals [SIZE_CHANGED_SIGNAL], 0,
+                size,
+                &handled);
+
+  return handled;
+}
+
+static GdkPixbuf *
+gtk_status_icon_blank_icon (GtkStatusIcon *status_icon)
+{
+  if (status_icon->priv->blank_icon)
+    {
+      gint width, height;
+
+      width  = gdk_pixbuf_get_width (status_icon->priv->blank_icon);
+      height = gdk_pixbuf_get_height (status_icon->priv->blank_icon);
+
+
+      if (width == status_icon->priv->image_width && 
+         height == status_icon->priv->image_height)
+       {
+         return status_icon->priv->blank_icon;
+       }
+      else
+       {
+         g_object_unref (status_icon->priv->blank_icon);
+         status_icon->priv->blank_icon = NULL;
+       }
+    }
+
+  status_icon->priv->blank_icon = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+                                                 status_icon->priv->image_width, 
+                                                 status_icon->priv->image_height);
+  if (status_icon->priv->blank_icon)
+    gdk_pixbuf_fill (status_icon->priv->blank_icon, 0);
+
+  return status_icon->priv->blank_icon;
+}
+
+static GtkIconSize
+find_icon_size (GtkWidget *widget, 
+               gint       pixel_size)
+{
+  GdkScreen *screen;
+  GtkSettings *settings;
+  GtkIconSize s, size;
+  gint w, h, d, dist;
+
+  screen = gtk_widget_get_screen (widget);
+
+  if (!screen)
+    return GTK_ICON_SIZE_MENU;
+
+  settings = gtk_settings_get_for_screen (screen);
+  
+  dist = G_MAXINT;
+  size = GTK_ICON_SIZE_MENU;
+
+  for (s = GTK_ICON_SIZE_MENU; s < GTK_ICON_SIZE_DIALOG; s++)
+    {
+      if (gtk_icon_size_lookup_for_settings (settings, s, &w, &h) &&
+         w <= pixel_size && h <= pixel_size)
+       {
+         d = MAX (pixel_size - w, pixel_size - h);
+         if (d < dist)
+           {
+             dist = d;
+             size = s;
+           }
+       }
+    }
+  
+  return size;
+}
+
+static void
+gtk_status_icon_update_image (GtkStatusIcon *status_icon)
+{
+  if (status_icon->priv->blink_off)
+    {
+      gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image),
+                                gtk_status_icon_blank_icon (status_icon));
+      return;
+    }
+
+  switch (status_icon->priv->storage_type)
+    {
+    case GTK_IMAGE_PIXBUF:
+      {
+       GdkPixbuf *pixbuf;
+
+       pixbuf = status_icon->priv->image_data.pixbuf;
+
+       if (pixbuf)
+         {
+           GdkPixbuf *scaled;
+           gint size;
+           gint width;
+           gint height;
+
+           size = status_icon->priv->size;
+
+           width  = gdk_pixbuf_get_width  (pixbuf);
+           height = gdk_pixbuf_get_height (pixbuf);
+
+           if (width > size || height > size)
+             {
+               scaled = gdk_pixbuf_scale_simple (pixbuf,
+                                                 MIN (size, width),
+                                                 MIN (size, height),
+                                                 GDK_INTERP_BILINEAR);
+             }
+           else
+             {
+               scaled = g_object_ref (pixbuf);
+             }
+
+           gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), scaled);
+
+           g_object_unref (scaled);
+         }
+       else
+         {
+           gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), NULL);
+         }
+      }
+      break;
+
+    case GTK_IMAGE_STOCK:
+      {
+       GtkIconSize size = find_icon_size (status_icon->priv->image, status_icon->priv->size);
+       gtk_image_set_from_stock (GTK_IMAGE (status_icon->priv->image),
+                                 status_icon->priv->image_data.stock_id,
+                                 size);
+      }
+      break;
+      
+    case GTK_IMAGE_ICON_NAME:
+      {
+       GtkIconSize size = find_icon_size (status_icon->priv->image, status_icon->priv->size);
+       gtk_image_set_from_icon_name (GTK_IMAGE (status_icon->priv->image),
+                                     status_icon->priv->image_data.icon_name,
+                                     size);
+      }
+      break;
+      
+    case GTK_IMAGE_EMPTY:
+      gtk_image_set_from_pixbuf (GTK_IMAGE (status_icon->priv->image), NULL);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static void
+gtk_status_icon_size_allocate (GtkStatusIcon *status_icon,
+                              GtkAllocation *allocation)
+{
+  GtkOrientation orientation;
+  gint size;
+
+  orientation = _gtk_tray_icon_get_orientation (GTK_TRAY_ICON (status_icon->priv->tray_icon));
+
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    size = allocation->height;
+  else
+    size = allocation->width;
+
+  status_icon->priv->image_width = allocation->width - GTK_MISC (status_icon->priv->image)->xpad * 2;
+  status_icon->priv->image_height = allocation->height - GTK_MISC (status_icon->priv->image)->ypad * 2;
+
+  if (status_icon->priv->size != size)
+    {
+      status_icon->priv->size = size;
+
+      g_object_notify (G_OBJECT (status_icon), "size");
+
+      if (!emit_size_changed_signal (status_icon, size))
+       {
+         gtk_status_icon_update_image (status_icon);
+       }
+    }
+}
+
+static gboolean
+gtk_status_icon_button_press (GtkStatusIcon  *status_icon,
+                             GdkEventButton *event)
+{
+  if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
+    {
+      emit_activate_signal (status_icon);
+      return TRUE;
+    }
+  else if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+    {
+      emit_popup_menu_signal (status_icon, event->button, event->time);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gtk_status_icon_reset_image_data (GtkStatusIcon *status_icon)
+{
+  status_icon->priv->storage_type = GTK_IMAGE_EMPTY;
+  g_object_notify (G_OBJECT (status_icon), "storage-type");
+
+  switch (status_icon->priv->storage_type)
+  {
+    case GTK_IMAGE_PIXBUF:
+      if (status_icon->priv->image_data.pixbuf)
+       g_object_unref (status_icon->priv->image_data.pixbuf);
+      status_icon->priv->image_data.pixbuf = NULL;
+      g_object_notify (G_OBJECT (status_icon), "pixbuf");
+      break;
+
+    case GTK_IMAGE_STOCK:
+      g_free (status_icon->priv->image_data.stock_id);
+      status_icon->priv->image_data.stock_id = NULL;
+
+      g_object_notify (G_OBJECT (status_icon), "stock");
+      break;
+      
+    case GTK_IMAGE_ICON_NAME:
+      g_free (status_icon->priv->image_data.icon_name);
+      status_icon->priv->image_data.icon_name = NULL;
+
+      g_object_notify (G_OBJECT (status_icon), "icon-name");
+      break;
+      
+    case GTK_IMAGE_EMPTY:
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+  }
+}
+
+static void
+gtk_status_icon_set_image (GtkStatusIcon *status_icon,
+                          GtkImageType   storage_type,
+                          gpointer       data)
+{
+  g_object_freeze_notify (G_OBJECT (status_icon));
+
+  gtk_status_icon_reset_image_data (status_icon);
+
+  status_icon->priv->storage_type = storage_type;
+  g_object_notify (G_OBJECT (status_icon), "storage-type");
+
+  switch (storage_type) 
+    {
+    case GTK_IMAGE_PIXBUF:
+      status_icon->priv->image_data.pixbuf = (GdkPixbuf *)data;
+      g_object_notify (G_OBJECT (status_icon), "pixbuf");
+      break;
+    case GTK_IMAGE_STOCK:
+      status_icon->priv->image_data.stock_id = g_strdup ((const gchar *)data);
+      g_object_notify (G_OBJECT (status_icon), "stock");
+      break;
+    case GTK_IMAGE_ICON_NAME:
+      status_icon->priv->image_data.icon_name = g_strdup ((const gchar *)data);
+      g_object_notify (G_OBJECT (status_icon), "icon-name");
+      break;
+    default:
+      g_warning ("Image type %d not handled by GtkStatusIcon", storage_type);
+    }
+
+  g_object_thaw_notify (G_OBJECT (status_icon));
+
+  gtk_status_icon_update_image (status_icon);
+}
+
+void
+gtk_status_icon_set_from_pixbuf (GtkStatusIcon *status_icon,
+                                GdkPixbuf     *pixbuf)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+  g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
+
+  if (pixbuf)
+    g_object_ref (pixbuf);
+
+  gtk_status_icon_set_image (status_icon, GTK_IMAGE_PIXBUF,
+                            (gpointer) pixbuf);
+}
+
+void
+gtk_status_icon_set_from_file (GtkStatusIcon *status_icon,
+                              const gchar   *filename)
+{
+  GdkPixbuf *pixbuf;
+  
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+  g_return_if_fail (filename != NULL);
+  
+  pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+  
+  gtk_status_icon_set_from_pixbuf (status_icon, pixbuf);
+  
+  if (pixbuf)
+    g_object_unref (pixbuf);
+}
+
+void
+gtk_status_icon_set_from_stock (GtkStatusIcon *status_icon,
+                               const gchar   *stock_id)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+  g_return_if_fail (stock_id != NULL);
+
+  gtk_status_icon_set_image (status_icon, GTK_IMAGE_STOCK,
+                            (gpointer) stock_id);
+}
+
+void
+gtk_status_icon_set_from_icon_name (GtkStatusIcon *status_icon,
+                                   const gchar   *icon_name)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+  g_return_if_fail (icon_name != NULL);
+
+  gtk_status_icon_set_image (status_icon, GTK_IMAGE_ICON_NAME,
+                            (gpointer) icon_name);
+}
+
+GtkImageType
+gtk_status_icon_get_storage_type (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), GTK_IMAGE_EMPTY);
+
+  return status_icon->priv->storage_type;
+}
+                                                                                                             
+GdkPixbuf *
+gtk_status_icon_get_pixbuf (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL);
+  g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_PIXBUF ||
+                       status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL);
+                                                                                                             
+  if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY)
+    status_icon->priv->image_data.pixbuf = NULL;
+                                                                                                             
+  return status_icon->priv->image_data.pixbuf;
+}
+
+G_CONST_RETURN gchar *
+gtk_status_icon_get_stock (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL);
+  g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_STOCK ||
+                       status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL);
+  
+  if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY)
+    status_icon->priv->image_data.stock_id = NULL;
+
+  return status_icon->priv->image_data.stock_id;
+}
+
+G_CONST_RETURN gchar *
+gtk_status_icon_get_icon_name (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), NULL);
+  g_return_val_if_fail (status_icon->priv->storage_type == GTK_IMAGE_ICON_NAME ||
+                       status_icon->priv->storage_type == GTK_IMAGE_EMPTY, NULL);
+
+  if (status_icon->priv->storage_type == GTK_IMAGE_EMPTY)
+    status_icon->priv->image_data.icon_name = NULL;
+
+  return status_icon->priv->image_data.icon_name;
+}
+
+gint
+gtk_status_icon_get_size (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), 0);
+
+  return status_icon->priv->size;
+}
+                                                                                                             
+void
+gtk_status_icon_set_tooltip (GtkStatusIcon *status_icon,
+                            const gchar   *tooltip_text)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+
+  gtk_tooltips_set_tip (status_icon->priv->tooltips,
+                       status_icon->priv->tray_icon,
+                       tooltip_text, NULL);
+}
+
+static gboolean
+gtk_status_icon_blinker (GtkStatusIcon *status_icon)
+{
+  status_icon->priv->blink_off = !status_icon->priv->blink_off;
+
+  gtk_status_icon_update_image (status_icon);
+
+  return TRUE;
+}
+
+static void
+gtk_status_icon_enable_blinking (GtkStatusIcon *status_icon)
+{
+  if (!status_icon->priv->blinking_timeout)
+    {
+      gtk_status_icon_blinker (status_icon);
+
+      status_icon->priv->blinking_timeout =
+       g_timeout_add (BLINK_TIMEOUT, 
+                      (GSourceFunc) gtk_status_icon_blinker, 
+                      status_icon);
+    }
+}
+
+static void
+gtk_status_icon_disable_blinking (GtkStatusIcon *status_icon)
+{
+  if (status_icon->priv->blinking_timeout)
+    {
+      g_source_remove (status_icon->priv->blinking_timeout);
+      status_icon->priv->blinking_timeout = 0;
+      status_icon->priv->blink_off = FALSE;
+
+      gtk_status_icon_update_image (status_icon);
+    }
+}
+
+void
+gtk_status_icon_set_visible (GtkStatusIcon *status_icon,
+                            gboolean       visible)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+
+  visible = visible != FALSE;
+
+  if (status_icon->priv->visible != visible)
+    {
+      status_icon->priv->visible = visible;
+
+      if (visible)
+       gtk_widget_show (status_icon->priv->tray_icon);
+      else
+       gtk_widget_hide (status_icon->priv->tray_icon);
+
+      g_object_notify (G_OBJECT (status_icon), "visible");
+    }
+}
+
+gboolean
+gtk_status_icon_get_visible (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE);
+
+  return status_icon->priv->visible;
+}
+
+void
+gtk_status_icon_set_blinking (GtkStatusIcon *status_icon,
+                             gboolean       blinking)
+{
+  g_return_if_fail (GTK_IS_STATUS_ICON (status_icon));
+
+  blinking = blinking != FALSE;
+
+  if (status_icon->priv->blinking != blinking)
+    {
+      status_icon->priv->blinking = blinking;
+
+      if (blinking)
+       gtk_status_icon_enable_blinking (status_icon);
+      else
+       gtk_status_icon_disable_blinking (status_icon);
+
+      g_object_notify (G_OBJECT (status_icon), "blinking");
+    }
+}
+
+gboolean
+gtk_status_icon_get_blinking (GtkStatusIcon *status_icon)
+{
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE);
+
+  return status_icon->priv->blinking;
+}
+
+gboolean
+gtk_status_icon_is_embedded (GtkStatusIcon *status_icon)
+{
+  GtkPlug *plug;
+
+  g_return_val_if_fail (GTK_IS_STATUS_ICON (status_icon), FALSE);
+
+  plug = GTK_PLUG (status_icon->priv->tray_icon);
+
+  if (plug->socket_window)
+    return TRUE;
+  else
+    return FALSE;
+}
+
+#define __GTK_STATUS_ICON_C__
+#include "gtkaliasdef.c"
diff --git a/gtk/gtkstatusicon.h b/gtk/gtkstatusicon.h
new file mode 100755 (executable)
index 0000000..f94f395
--- /dev/null
@@ -0,0 +1,108 @@
+/* gtkstatusicon.h:
+ *
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *      Mark McLoughlin <mark@skynet.ie>
+ */
+
+#ifndef __GTK_STATUS_ICON_H__
+#define __GTK_STATUS_ICON_H__
+
+#include <gtk/gtkimage.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_STATUS_ICON         (gtk_status_icon_get_type ())
+#define GTK_STATUS_ICON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_STATUS_ICON, GtkStatusIcon))
+#define GTK_STATUS_ICON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_STATUS_ICON, GtkStatusIconClass))
+#define GTK_IS_STATUS_ICON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_STATUS_ICON))
+#define GTK_IS_STATUS_ICON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_STATUS_ICON))
+#define GTK_STATUS_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_STATUS_ICON, GtkStatusIconClass))
+
+typedef struct _GtkStatusIcon       GtkStatusIcon;
+typedef struct _GtkStatusIconClass   GtkStatusIconClass;
+typedef struct _GtkStatusIconPrivate GtkStatusIconPrivate;
+
+struct _GtkStatusIcon
+{
+  GObject               parent_instance;
+
+  GtkStatusIconPrivate *priv;
+};
+
+struct _GtkStatusIconClass
+{
+  GObjectClass parent_class;
+
+  void     (* activate)     (GtkStatusIcon *status_icon);
+  void     (* popup_menu)   (GtkStatusIcon *status_icon,
+                            guint          buttton,
+                            guint32        activate_time);
+  gboolean (* size_changed) (GtkStatusIcon *status_icon,
+                            gint           size);
+
+  void (*__gtk_reserved1);
+  void (*__gtk_reserved2);
+  void (*__gtk_reserved3);
+  void (*__gtk_reserved4);
+  void (*__gtk_reserved5);
+  void (*__gtk_reserved6);  
+};
+
+GType                 gtk_status_icon_get_type           (void) G_GNUC_CONST;
+
+GtkStatusIcon        *gtk_status_icon_new                (void);
+GtkStatusIcon        *gtk_status_icon_new_from_pixbuf    (GdkPixbuf          *pixbuf);
+GtkStatusIcon        *gtk_status_icon_new_from_file      (const gchar        *filename);
+GtkStatusIcon        *gtk_status_icon_new_from_stock     (const gchar        *stock_id);
+GtkStatusIcon        *gtk_status_icon_new_from_icon_name (const gchar        *icon_name);
+
+void                  gtk_status_icon_set_from_pixbuf    (GtkStatusIcon      *status_icon,
+                                                         GdkPixbuf          *pixbuf);
+void                  gtk_status_icon_set_from_file      (GtkStatusIcon      *status_icon,
+                                                         const gchar        *filename);
+void                  gtk_status_icon_set_from_stock     (GtkStatusIcon      *status_icon,
+                                                         const gchar        *stock_id);
+void                  gtk_status_icon_set_from_icon_name (GtkStatusIcon      *status_icon,
+                                                         const gchar        *icon_name);
+
+GtkImageType          gtk_status_icon_get_storage_type   (GtkStatusIcon      *status_icon);
+
+GdkPixbuf            *gtk_status_icon_get_pixbuf         (GtkStatusIcon      *status_icon);
+G_CONST_RETURN gchar *gtk_status_icon_get_stock          (GtkStatusIcon      *status_icon);
+G_CONST_RETURN gchar *gtk_status_icon_get_icon_name      (GtkStatusIcon      *status_icon);
+
+gint                  gtk_status_icon_get_size           (GtkStatusIcon      *status_icon);
+
+void                  gtk_status_icon_set_tooltip        (GtkStatusIcon      *status_icon,
+                                                         const gchar        *tooltip_text);
+
+void                  gtk_status_icon_set_visible        (GtkStatusIcon      *status_icon,
+                                                         gboolean            visible);
+gboolean              gtk_status_icon_get_visible        (GtkStatusIcon      *status_icon);
+
+void                  gtk_status_icon_set_blinking       (GtkStatusIcon      *status_icon,
+                                                         gboolean            blinking);
+gboolean              gtk_status_icon_get_blinking       (GtkStatusIcon      *status_icon);
+
+gboolean              gtk_status_icon_is_embedded        (GtkStatusIcon      *status_icon);
+
+G_END_DECLS
+
+#endif /* __GTK_STATUS_ICON_H__ */
diff --git a/gtk/gtktrayicon-x11.c b/gtk/gtktrayicon-x11.c
new file mode 100755 (executable)
index 0000000..8f42c9d
--- /dev/null
@@ -0,0 +1,498 @@
+/* gtktrayicon.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <libintl.h>
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+#include "gtktrayicon.h"
+
+#include "gtkalias.h"
+
+#include "x11/gdkx.h"
+#include <X11/Xatom.h>
+
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+enum {
+  PROP_0,
+  PROP_ORIENTATION
+};
+
+struct _GtkTrayIconPrivate
+{
+  guint stamp;
+  
+  Atom selection_atom;
+  Atom manager_atom;
+  Atom system_tray_opcode_atom;
+  Atom orientation_atom;
+  Window manager_window;
+
+  GtkOrientation orientation;
+};
+         
+static void gtk_tray_icon_get_property  (GObject     *object,
+                                        guint        prop_id,
+                                        GValue      *value,
+                                        GParamSpec  *pspec);
+
+static void     gtk_tray_icon_realize   (GtkWidget   *widget);
+static void     gtk_tray_icon_unrealize (GtkWidget   *widget);
+static gboolean gtk_tray_icon_delete    (GtkWidget   *widget,
+                                        GdkEventAny *event);
+
+static void gtk_tray_icon_update_manager_window    (GtkTrayIcon *icon,
+                                                   gboolean     dock_if_realized);
+static void gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon);
+
+G_DEFINE_TYPE (GtkTrayIcon, gtk_tray_icon, GTK_TYPE_PLUG);
+
+static void
+gtk_tray_icon_class_init (GtkTrayIconClass *class)
+{
+  GObjectClass *gobject_class = (GObjectClass *)class;
+  GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
+
+  gobject_class->get_property = gtk_tray_icon_get_property;
+
+  widget_class->realize   = gtk_tray_icon_realize;
+  widget_class->unrealize = gtk_tray_icon_unrealize;
+  widget_class->delete_event = gtk_tray_icon_delete;
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_ORIENTATION,
+                                  g_param_spec_enum ("orientation",
+                                                     P_("Orientation"),
+                                                     P_("The orientation of the tray"),
+                                                     GTK_TYPE_ORIENTATION,
+                                                     GTK_ORIENTATION_HORIZONTAL,
+                                                     GTK_PARAM_READABLE));
+
+  g_type_class_add_private (class, sizeof (GtkTrayIconPrivate));
+}
+
+static void
+gtk_tray_icon_init (GtkTrayIcon *icon)
+{
+  icon->priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, GTK_TYPE_TRAY_ICON,
+                                           GtkTrayIconPrivate);
+  
+  icon->priv->stamp = 1;
+  icon->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+  
+  gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
+}
+
+static void
+gtk_tray_icon_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GtkTrayIcon *icon = GTK_TRAY_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, icon->priv->orientation);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_tray_icon_get_orientation_property (GtkTrayIcon *icon)
+{
+  Display *xdisplay;
+  Atom type;
+  int format;
+  union {
+       gulong *prop;
+       guchar *prop_ch;
+  } prop = { NULL };
+  gulong nitems;
+  gulong bytes_after;
+  int error, result;
+
+  g_assert (icon->priv->manager_window != None);
+  
+  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+  gdk_error_trap_push ();
+  type = None;
+  result = XGetWindowProperty (xdisplay,
+                              icon->priv->manager_window,
+                              icon->priv->orientation_atom,
+                              0, G_MAXLONG, FALSE,
+                              XA_CARDINAL,
+                              &type, &format, &nitems,
+                              &bytes_after, &(prop.prop_ch));
+  error = gdk_error_trap_pop ();
+
+  if (error || result != Success)
+    return;
+
+  if (type == XA_CARDINAL)
+    {
+      GtkOrientation orientation;
+
+      orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
+                                       GTK_ORIENTATION_HORIZONTAL :
+                                       GTK_ORIENTATION_VERTICAL;
+
+      if (icon->priv->orientation != orientation)
+       {
+         icon->priv->orientation = orientation;
+
+         g_object_notify (G_OBJECT (icon), "orientation");
+       }
+    }
+
+  if (prop.prop)
+    XFree (prop.prop);
+}
+
+static GdkFilterReturn
+gtk_tray_icon_manager_filter (GdkXEvent *xevent, 
+                             GdkEvent  *event, 
+                             gpointer   user_data)
+{
+  GtkTrayIcon *icon = user_data;
+  XEvent *xev = (XEvent *)xevent;
+
+  if (xev->xany.type == ClientMessage &&
+      xev->xclient.message_type == icon->priv->manager_atom &&
+      xev->xclient.data.l[1] == icon->priv->selection_atom)
+    {
+      gtk_tray_icon_update_manager_window (icon, TRUE);
+    }
+  else if (xev->xany.window == icon->priv->manager_window)
+    {
+      if (xev->xany.type == PropertyNotify &&
+         xev->xproperty.atom == icon->priv->orientation_atom)
+       {
+         gtk_tray_icon_get_orientation_property (icon);
+       }
+      if (xev->xany.type == DestroyNotify)
+       {
+         gtk_tray_icon_manager_window_destroyed (icon);
+       }
+    }
+  
+  return GDK_FILTER_CONTINUE;
+}
+
+static void
+gtk_tray_icon_unrealize (GtkWidget *widget)
+{
+  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
+  GdkWindow *root_window;
+
+  if (icon->priv->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
+                                              icon->priv->manager_window);
+      
+      gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
+    }
+
+  root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
+
+  gdk_window_remove_filter (root_window, gtk_tray_icon_manager_filter, icon);
+
+  if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->unrealize)
+    (* GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_tray_icon_send_manager_message (GtkTrayIcon *icon,
+                                   long         message,
+                                   Window       window,
+                                   long         data1,
+                                   long         data2,
+                                   long         data3)
+{
+  XClientMessageEvent ev;
+  Display *display;
+  
+  ev.type = ClientMessage;
+  ev.window = window;
+  ev.message_type = icon->priv->system_tray_opcode_atom;
+  ev.format = 32;
+  ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
+  ev.data.l[1] = message;
+  ev.data.l[2] = data1;
+  ev.data.l[3] = data2;
+  ev.data.l[4] = data3;
+
+  display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+  
+  gdk_error_trap_push ();
+  XSendEvent (display,
+             icon->priv->manager_window, False, NoEventMask, (XEvent *)&ev);
+  XSync (display, False);
+  gdk_error_trap_pop ();
+}
+
+static void
+gtk_tray_icon_send_dock_request (GtkTrayIcon *icon)
+{
+  gtk_tray_icon_send_manager_message (icon,
+                                     SYSTEM_TRAY_REQUEST_DOCK,
+                                     icon->priv->manager_window,
+                                     gtk_plug_get_id (GTK_PLUG (icon)),
+                                     0, 0);
+}
+
+static void
+gtk_tray_icon_update_manager_window (GtkTrayIcon *icon,
+                                    gboolean     dock_if_realized)
+{
+  Display *xdisplay;
+  
+  if (icon->priv->manager_window != None)
+    return;
+
+  xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+  
+  XGrabServer (xdisplay);
+  
+  icon->priv->manager_window = XGetSelectionOwner (xdisplay,
+                                                  icon->priv->selection_atom);
+
+  if (icon->priv->manager_window != None)
+    XSelectInput (xdisplay,
+                 icon->priv->manager_window, StructureNotifyMask|PropertyChangeMask);
+
+  XUngrabServer (xdisplay);
+  XFlush (xdisplay);
+  
+  if (icon->priv->manager_window != None)
+    {
+      GdkWindow *gdkwin;
+
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+                                             icon->priv->manager_window);
+      
+      gdk_window_add_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
+
+      if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
+       gtk_tray_icon_send_dock_request (icon);
+
+      gtk_tray_icon_get_orientation_property (icon);
+    }
+}
+
+static void
+gtk_tray_icon_manager_window_destroyed (GtkTrayIcon *icon)
+{
+  GdkWindow *gdkwin;
+  
+  g_return_if_fail (icon->priv->manager_window != None);
+
+  gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+                                         icon->priv->manager_window);
+      
+  gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
+
+  icon->priv->manager_window = None;
+
+  gtk_tray_icon_update_manager_window (icon, TRUE);
+}
+
+static gboolean 
+gtk_tray_icon_delete (GtkWidget   *widget,
+                     GdkEventAny *event)
+{
+  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
+  GdkWindow *gdkwin;
+
+  if (icon->priv->manager_window != None)
+    {  
+      gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+                                             icon->priv->manager_window);
+      
+      gdk_window_remove_filter (gdkwin, gtk_tray_icon_manager_filter, icon);
+      
+      icon->priv->manager_window = None;
+    }
+
+  gtk_tray_icon_update_manager_window (icon, TRUE);  
+
+  return TRUE;
+}
+
+static void
+gtk_tray_icon_realize (GtkWidget *widget)
+{
+  GtkTrayIcon *icon = GTK_TRAY_ICON (widget);
+  GdkScreen *screen;
+  GdkDisplay *display;
+  Display *xdisplay;
+  char buffer[256];
+  GdkWindow *root_window;
+
+  if (GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize)
+    GTK_WIDGET_CLASS (gtk_tray_icon_parent_class)->realize (widget);
+
+  screen = gtk_widget_get_screen (widget);
+  display = gdk_screen_get_display (screen);
+  xdisplay = gdk_x11_display_get_xdisplay (display);
+
+  /* Now see if there's a manager window around */
+  g_snprintf (buffer, sizeof (buffer),
+             "_NET_SYSTEM_TRAY_S%d",
+             gdk_screen_get_number (screen));
+
+  icon->priv->selection_atom = XInternAtom (xdisplay, buffer, False);
+  
+  icon->priv->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
+  
+  icon->priv->system_tray_opcode_atom = XInternAtom (xdisplay,
+                                                    "_NET_SYSTEM_TRAY_OPCODE",
+                                                    False);
+
+  icon->priv->orientation_atom = XInternAtom (xdisplay,
+                                             "_NET_SYSTEM_TRAY_ORIENTATION",
+                                             False);
+
+  gtk_tray_icon_update_manager_window (icon, FALSE);
+  gtk_tray_icon_send_dock_request (icon);
+
+  root_window = gdk_screen_get_root_window (screen);
+  
+  /* Add a root window filter so that we get changes on MANAGER */
+  gdk_window_add_filter (root_window,
+                        gtk_tray_icon_manager_filter, icon);
+}
+
+guint
+_gtk_tray_icon_send_message (GtkTrayIcon *icon,
+                            gint         timeout,
+                            const gchar *message,
+                            gint         len)
+{
+  guint stamp;
+  
+  g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), 0);
+  g_return_val_if_fail (timeout >= 0, 0);
+  g_return_val_if_fail (message != NULL, 0);
+                    
+  if (icon->priv->manager_window == None)
+    return 0;
+
+  if (len < 0)
+    len = strlen (message);
+
+  stamp = icon->priv->stamp++;
+  
+  /* Get ready to send the message */
+  gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
+                                     icon->priv->manager_window,
+                                     timeout, len, stamp);
+
+  /* Now to send the actual message */
+  gdk_error_trap_push ();
+  while (len > 0)
+    {
+      XClientMessageEvent ev;
+      Display *xdisplay;
+
+      xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+      
+      ev.type = ClientMessage;
+      ev.window = icon->priv->manager_window;
+      ev.format = 8;
+      ev.message_type = XInternAtom (xdisplay,
+                                    "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
+      if (len > 20)
+       {
+         memcpy (&ev.data, message, 20);
+         len -= 20;
+         message += 20;
+       }
+      else
+       {
+         memcpy (&ev.data, message, len);
+         len = 0;
+       }
+
+      XSendEvent (xdisplay,
+                 icon->priv->manager_window, False, 
+                 StructureNotifyMask, (XEvent *)&ev);
+      XSync (xdisplay, False);
+    }
+
+  gdk_error_trap_pop ();
+
+  return stamp;
+}
+
+void
+_gtk_tray_icon_cancel_message (GtkTrayIcon *icon,
+                              guint        id)
+{
+  g_return_if_fail (GTK_IS_TRAY_ICON (icon));
+  g_return_if_fail (id > 0);
+  
+  gtk_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
+                                     icon->priv->manager_window,
+                                     id, 0, 0);
+}
+
+GtkTrayIcon *
+_gtk_tray_icon_new_for_screen (GdkScreen  *screen, 
+                              const gchar *name)
+{
+  g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+  return g_object_new (GTK_TYPE_TRAY_ICON, 
+                      "screen", screen, 
+                      "title", name, 
+                      NULL);
+}
+
+GtkTrayIcon*
+_gtk_tray_icon_new (const gchar *name)
+{
+  return g_object_new (GTK_TYPE_TRAY_ICON, 
+                      "title", name, 
+                      NULL);
+}
+
+GtkOrientation
+_gtk_tray_icon_get_orientation (GtkTrayIcon *icon)
+{
+  g_return_val_if_fail (GTK_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
+
+  return icon->priv->orientation;
+}
+
diff --git a/gtk/gtktrayicon.h b/gtk/gtktrayicon.h
new file mode 100755 (executable)
index 0000000..4c1e184
--- /dev/null
@@ -0,0 +1,75 @@
+/* gtktrayicon.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_TRAY_ICON_H__
+#define __GTK_TRAY_ICON_H__
+
+#include "gtkplug.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TRAY_ICON             (gtk_tray_icon_get_type ())
+#define GTK_TRAY_ICON(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TRAY_ICON, GtkTrayIcon))
+#define GTK_TRAY_ICON_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TRAY_ICON, GtkTrayIconClass))
+#define GTK_IS_TRAY_ICON(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TRAY_ICON))
+#define GTK_IS_TRAY_ICON_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TRAY_ICON))
+#define GTK_TRAY_ICON_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TRAY_ICON, GtkTrayIconClass))
+       
+typedef struct _GtkTrayIcon       GtkTrayIcon;
+typedef struct _GtkTrayIconPrivate GtkTrayIconPrivate;
+typedef struct _GtkTrayIconClass   GtkTrayIconClass;
+
+struct _GtkTrayIcon
+{
+  GtkPlug parent_instance;
+
+  GtkTrayIconPrivate *priv;
+};
+
+struct _GtkTrayIconClass
+{
+  GtkPlugClass parent_class;
+
+  void (*__gtk_reserved1);
+  void (*__gtk_reserved2);
+  void (*__gtk_reserved3);
+  void (*__gtk_reserved4);
+  void (*__gtk_reserved5);
+  void (*__gtk_reserved6);
+};
+
+GType          gtk_tray_icon_get_type         (void) G_GNUC_CONST;
+
+GtkTrayIcon   *_gtk_tray_icon_new_for_screen  (GdkScreen   *screen,
+                                              const gchar *name);
+
+GtkTrayIcon   *_gtk_tray_icon_new             (const gchar *name);
+
+guint          _gtk_tray_icon_send_message    (GtkTrayIcon *icon,
+                                              gint         timeout,
+                                              const gchar *message,
+                                              gint         len);
+void           _gtk_tray_icon_cancel_message  (GtkTrayIcon *icon,
+                                              guint        id);
+
+GtkOrientation _gtk_tray_icon_get_orientation (GtkTrayIcon *icon);
+                                           
+G_END_DECLS
+
+#endif /* __GTK_TRAY_ICON_H__ */
index ea7a8855185f7daf3511b99ef06991866fe0c931..c54663a99ca290252c4776a56416cfe7c099936a 100644 (file)
@@ -50,6 +50,7 @@ noinst_PROGRAMS =                     \
        testselection                   \
        $(testsocket_programs)          \
        testspinbutton                  \
+       teststatusicon                  \
        testtext                        \
         testtextbuffer                 \
        testtoolbar                     \
@@ -95,6 +96,7 @@ testselection_DEPENDENCIES = $(TEST_DEPS)
 testsocket_DEPENDENCIES = $(DEPS)
 testsocket_child_DEPENDENCIES = $(DEPS)
 testspinbutton_DEPENDENCIES = $(TEST_DEPS)
+teststatusicon_DEPENDENCIES = $(TEST_DEPS)
 testtext_DEPENDENCIES = $(TEST_DEPS)
 testtextbuffer_DEPENDENCIES = $(TEST_DEPS)
 testtreeedit_DEPENDENCIES = $(DEPS)
@@ -133,6 +135,7 @@ testselection_LDADD = $(LDADDS)
 testsocket_LDADD = $(LDADDS)
 testsocket_child_LDADD = $(LDADDS)
 testspinbutton_LDADD = $(LDADDS)
+teststatusicon_LDADD = $(LDADDS)
 testtextbuffer_LDADD = $(LDADDS)
 testtoolbar_LDADD = $(LDADDS)
 stresstest_toolbar_LDADD = $(LDADDS)
@@ -195,6 +198,9 @@ testsocket_child_SOURCES =          \
 testspinbutton_SOURCES =       \
        testspinbutton.c
 
+teststatusicon_SOURCES =       \
+       teststatusicon.c
+
 testmerge_SOURCES =            \
        testmerge.c
 
diff --git a/tests/teststatusicon.c b/tests/teststatusicon.c
new file mode 100755 (executable)
index 0000000..467baf5
--- /dev/null
@@ -0,0 +1,209 @@
+/* gtkstatusicon-x11.c:
+ *
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin <mark@skynet.ie>
+ */
+
+#include <gtk/gtk.h>
+
+typedef enum
+{
+  TEST_STATUS_FILE,
+  TEST_STATUS_DIRECTORY
+} TestStatus;
+
+static TestStatus status = TEST_STATUS_FILE;
+static gint timeout = 0;
+
+static void
+update_icon (GtkStatusIcon *status_icon)
+{
+  gchar *icon_name;
+  gchar *tooltip;
+
+  if (status == TEST_STATUS_FILE)
+    {
+      icon_name = "gnome-fs-regular";
+      tooltip = "Regular File";
+    }
+  else
+    {
+      icon_name = "gnome-fs-directory";
+      tooltip = "Directory";
+    }
+
+  gtk_status_icon_set_from_icon_name (status_icon, icon_name);
+  gtk_status_icon_set_tooltip (status_icon, tooltip);
+}
+
+static gboolean
+timeout_handler (gpointer data)
+{
+  GtkStatusIcon *icon = GTK_STATUS_ICON (data);
+
+  if (status == TEST_STATUS_FILE)
+    status = TEST_STATUS_DIRECTORY;
+  else
+    status = TEST_STATUS_FILE;
+
+  update_icon (icon);
+
+  return TRUE;
+}
+
+static void
+blink_toggle_toggled (GtkToggleButton *toggle,
+                     GtkStatusIcon   *icon)
+{
+  gtk_status_icon_set_blinking (icon, 
+                               gtk_toggle_button_get_active (toggle));
+}
+
+static void
+visible_toggle_toggled (GtkToggleButton *toggle,
+                       GtkStatusIcon   *icon)
+{
+  gtk_status_icon_set_visible (icon, 
+                              gtk_toggle_button_get_active (toggle));
+}
+
+static void
+timeout_toggle_toggled (GtkToggleButton *toggle,
+                       GtkStatusIcon   *icon)
+{
+  if (timeout)
+    {
+      g_source_remove (timeout);
+      timeout = 0;
+    }
+  else
+    {
+      timeout = g_timeout_add (2000, timeout_handler, icon);
+    }
+}
+
+static void
+icon_activated (GtkStatusIcon *icon)
+{
+  GtkWidget *dialog;
+  GtkWidget *toggle;
+
+  dialog = g_object_get_data (G_OBJECT (icon), "test-status-icon-dialog");
+  if (dialog == NULL)
+    {
+      dialog = gtk_message_dialog_new (NULL, 0,
+                                      GTK_MESSAGE_QUESTION,
+                                      GTK_BUTTONS_CLOSE,
+                                      "You wanna test the status icon ?");
+
+      g_object_set_data_full (G_OBJECT (icon), "test-status-icon-dialog",
+                             dialog, (GDestroyNotify) gtk_widget_destroy);
+
+      g_signal_connect (dialog, "response", 
+                       G_CALLBACK (gtk_widget_hide), NULL);
+      g_signal_connect (dialog, "delete_event", 
+                       G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+
+      toggle = gtk_toggle_button_new_with_mnemonic ("_Show the icon");
+      gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6);
+      gtk_widget_show (toggle);
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                   gtk_status_icon_get_visible (icon));
+      g_signal_connect (toggle, "toggled", 
+                       G_CALLBACK (visible_toggle_toggled), icon);
+
+      toggle = gtk_toggle_button_new_with_mnemonic ("_Blink the icon");
+      gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6);
+      gtk_widget_show (toggle);
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                   gtk_status_icon_get_blinking (icon));
+      g_signal_connect (toggle, "toggled", 
+                       G_CALLBACK (blink_toggle_toggled), icon);
+
+      toggle = gtk_toggle_button_new_with_mnemonic ("_Change images");
+      gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox), toggle, TRUE, TRUE, 6);
+      gtk_widget_show (toggle);
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                   timeout != 0);
+      g_signal_connect (toggle, "toggled", 
+                       G_CALLBACK (timeout_toggle_toggled), icon);
+    }
+
+  gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+check_activated (GtkCheckMenuItem *item,
+                GtkStatusIcon    *icon)
+{
+  gtk_status_icon_set_blinking (icon, 
+                               gtk_check_menu_item_get_active (item));
+}
+
+static void 
+popup_menu (GtkStatusIcon *icon,
+           guint          button,
+           guint32        activate_time)
+{
+  GtkWidget *menu, *menuitem;
+
+  menu = gtk_menu_new ();
+
+  menuitem = gtk_check_menu_item_new_with_label ("Blink");
+  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem), 
+                                 gtk_status_icon_get_blinking (icon));
+  g_signal_connect (menuitem, "activate", G_CALLBACK (check_activated), icon);
+
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+  gtk_widget_show (menuitem);
+
+  gtk_menu_popup (GTK_MENU (menu), 
+                 NULL, NULL, NULL, NULL, 
+                 button, activate_time);
+}
+
+int
+main (int argc, char **argv)
+{
+  GtkStatusIcon *icon;
+
+  gtk_init (&argc, &argv);
+
+  icon = gtk_status_icon_new ();
+  update_icon (icon);
+
+  gtk_status_icon_set_blinking (GTK_STATUS_ICON (icon), TRUE);
+
+  g_signal_connect (icon, "activate",
+                   G_CALLBACK (icon_activated), NULL);
+
+  g_signal_connect (icon, "popup-menu",
+                   G_CALLBACK (popup_menu), NULL);
+  timeout = g_timeout_add (2000, timeout_handler, icon);
+
+  gtk_main ();
+
+  return 0;
+}